#ifndef _H_STEEL_IMAGE
#define _H_STEEL_IMAGE

#include <string>
#include "GL/glew.h"
#include "jpeglib.h" // Jpeg Format
#include "png.h" // PNG Format
#include "common.h"
#include "io_file.h"
#include "exception.h"

//#include "jpeg/jpeglib.h"
//#include "dds/DDS.h"

namespace _STEEL_NAMESPACE {

class Image {
 public:
	Image();
	~Image();

	// accesors
	GLsizei width() { return _width; };
	GLsizei height() { return _height; };
	GLuint channels() { return _channels; };
	GLuint mipmaps() { return _mipmaps_count; };
	GLuint format() { return _format; };
	GLubyte *pixels() { return _pixels; };
	bool loaded() { return _loaded; }

  bool IsCompressed() {
    if ((_format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ||
			(_format == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT) ||
			(_format == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT))
			return true;
		else
			return false;
	}

protected:
	std::string _name; ///< image filename

	GLsizei _width; ///< image width
	GLsizei _height; ///< image height

	/** image channels count (1 - Luminance, 3 - Red, Green and Blue channels,
    4 - Red, Green, Blue and Alpha channels) */
	GLuint _channels;
	/** size of image pixels data buffer. Usually it is equal to
	  width * height * channels count */
	GLuint _dataSize;
	/** image format GL_LUMINANCE, GL_RGB or GL_RGBA */
	GLuint _format;
	/** using only for DDS pregenerated mipmaps*/
	GLint _mipmaps_count;
  /** Image sucessfully loaded */
	bool _loaded;

  /** Image pixels data buffer*/
	GLubyte *_pixels;
};

// TarGA
class TGAImage : public Image {
 public:
	explicit TGAImage(IOFile &data);
	~TGAImage();

 private:
	#pragma pack(push, 1)
	struct TGAHeader {
		union {
		  struct {
        uint8_t ident_size; ///< size of ID field that follows 18 byte header (0 usually)
        uint8_t colormap_type; ///< type of colormap 0=none, 1=has palette
        uint8_t image_type; ///< type of image 0=none, 1=indexed, 2=rgb, 3=grey, +8=rle packed

        short colormap_start; ///< first colormap entry in palette
        short colormap_length; ///< number of colors in palette
        uint8_t colormap_bits; /// number of bits per palette entry 15, 16, 24, 32

        short x_start; ///< image x origin
        short y_start; ///< image y origin
        short width; ///< image width in pixels
        short height; ///< image height in pixels
        uint8_t bits; ///< image bits per pixel 8, 16, 24, 32
        uint8_t descriptor; ///< image descriptor bits (vh flip bits)
		  };
      GLubyte data[18];
		};
	};
	#pragma pack(pop)
};

// Jpeg
class JPGImage : public Image {
 public:
  explicit JPGImage(IOFile &data);
	~JPGImage();

 private:
	// callbacks functions for reading data
	static void InitSource(j_decompress_ptr cinfo);
	static bool FillInputBuffer(j_decompress_ptr cinfo);
	static void SkipInputData(j_decompress_ptr cinfo, long num_bytes);
	static void TermSource(j_decompress_ptr cinfo);

	// error handling callbacks
	static void ErrorExit (j_common_ptr cinfo);
	static void OutputMessage (j_common_ptr cinfo);
};

// DDS
//  little-endian, of course
#define DDS_MAGIC 0x20534444

//  DDS_header.dwFlags
#define DDSD_CAPS                   0x00000001
#define DDSD_HEIGHT                 0x00000002
#define DDSD_WIDTH                  0x00000004
#define DDSD_PITCH                  0x00000008
#define DDSD_PIXELFORMAT            0x00001000
#define DDSD_MIPMAPCOUNT            0x00020000
#define DDSD_LINEARSIZE             0x00080000
#define DDSD_DEPTH                  0x00800000

//  DDS_header.sPixelFormat.dwFlags
#define DDPF_ALPHAPIXELS            0x00000001
#define DDPF_FOURCC                 0x00000004
#define DDPF_INDEXED                0x00000020
#define DDPF_RGB                    0x00000040

//  DDS_header.sCaps.dwCaps1
#define DDSCAPS_COMPLEX             0x00000008
#define DDSCAPS_TEXTURE             0x00001000
#define DDSCAPS_MIPMAP              0x00400000

//  DDS_header.sCaps.dwCaps2
#define DDSCAPS2_CUBEMAP            0x00000200
#define DDSCAPS2_CUBEMAP_POSITIVEX  0x00000400
#define DDSCAPS2_CUBEMAP_NEGATIVEX  0x00000800
#define DDSCAPS2_CUBEMAP_POSITIVEY  0x00001000
#define DDSCAPS2_CUBEMAP_NEGATIVEY  0x00002000
#define DDSCAPS2_CUBEMAP_POSITIVEZ  0x00004000
#define DDSCAPS2_CUBEMAP_NEGATIVEZ  0x00008000
#define DDSCAPS2_VOLUME             0x00200000

#define D3DFMT_DXT1     '1TXD'    //  DXT1 compression texture format
#define D3DFMT_DXT2     '2TXD'    //  DXT2 compression texture format
#define D3DFMT_DXT3     '3TXD'    //  DXT3 compression texture format
#define D3DFMT_DXT4     '4TXD'    //  DXT4 compression texture format
#define D3DFMT_DXT5     '5TXD'    //  DXT5 compression texture format

#define PF_IS_DXT1(pf) \
  ((pf.flags & DDPF_FOURCC) && \
   (pf.fourCC == D3DFMT_DXT1))

#define PF_IS_DXT3(pf) \
  ((pf.flags & DDPF_FOURCC) && \
   (pf.fourCC == D3DFMT_DXT3))

#define PF_IS_DXT5(pf) \
  ((pf.flags & DDPF_FOURCC) && \
   (pf.fourCC == D3DFMT_DXT5))

#define PF_IS_BGRA8(pf) \
  ((pf.flags & DDPF_RGB) && \
   (pf.flags & DDPF_ALPHAPIXELS) && \
   (pf.rgb_bit_count == 32) && \
   (pf.red_bit_mask == 0xff0000) && \
   (pf.green_bit_mask == 0xff00) && \
   (pf.blue_bit_mask == 0xff) && \
   (pf.alpha_bit_mask == 0xff000000U))

#define PF_IS_BGR8(pf) \
  ((pf.flags & DDPF_ALPHAPIXELS) && \
  !(pf.flags & DDPF_ALPHAPIXELS) && \
   (pf.rgb_bit_count == 24) && \
   (pf.r_bit_mask == 0xff0000) && \
   (pf.g_bitmask == 0xff00) && \
   (pf.b_bit_mask == 0xff))

#define PF_IS_BGR5A1(pf) \
  ((pf.flags & DDPF_RGB) && \
   (pf.flags & DDPF_ALPHAPIXELS) && \
   (pf.rgb_bit_count == 16) && \
   (pf.red_bit_mask == 0x00007c00) && \
   (pf.green_bit_mask == 0x000003e0) && \
   (pf.blue_bit_mask == 0x0000001f) && \
   (pf.alpha_bit_mask == 0x00008000))

#define PF_IS_BGR565(pf) \
  ((pf.flags & DDPF_RGB) && \
  !(pf.flags & DDPF_ALPHAPIXELS) && \
   (pf.rgb_bit_count == 16) && \
   (pf.r_bit_mask == 0x0000f800) && \
   (pf.g_bit_mask == 0x000007e0) && \
   (pf.b_bit_mask == 0x0000001f))

#define PF_IS_INDEX8(pf) \
  ((pf.flags & DDPF_INDEXED) && \
   (pf.rgb_bit_count == 8))

class DDSImage : public Image {
 public:
  explicit DDSImage(IOFile & data);
	~DDSImage();
 private:
	struct DDSInfo {
		bool compressed;
		bool swap;
		bool palette;
		uint32_t divSize;
		uint32_t blockBytes;
		GLenum internal_format;
		GLenum external_format;
		GLenum type;
	};
	//
	union DDSHeader {
    struct {
      uint32_t magic;
      uint32_t size;
      uint32_t flags;
      uint32_t height;
      uint32_t width;
      uint32_t pitch_or_linear_size;
      uint32_t depth;
      uint32_t mipmaps_count;
      uint32_t reserved_1[11];

      //  DDPIXELFORMAT
      struct {
        uint32_t size;
        uint32_t flags;
        uint32_t fourCC;
        uint32_t rgb_bit_count;
        uint32_t red_bit_mask;
        uint32_t green_bit_mask;
        uint32_t blue_bit_mask;
        uint32_t alpha_bit_mask;
      } pixel_format;

      //  DDCAPS2
      struct {
        uint32_t caps1;
        uint32_t caps2;
        uint32_t DDSX;
        uint32_t reserved;
      } caps;

      uint32_t reserved_2;
		};
		GLubyte data[128];
	};
};

// PNG?
class PNGImage : public Image {
 public:
  PNGImage(IOFile &data);
  ~PNGImage();
};

// Image Loader
class ImageLoader {
 public:
  Image *LoadImage(const char *filename) {
    Image *image;
    IOFile data(filename, "rb");
    char *ext;

    if (!data.Opened())
      throw RuntimeError() << "Can not open image file '" << filename << "'";

    ext = strrchr(const_cast<char *>(filename), '.');
    if (ext != NULL) {
      ext++;

      if (strcmp(ext, "tga") == 0) {
        image = new TGAImage(data);
      } else if (strcmp(ext, "jpg") == 0) {
        image = new JPGImage(data);
      } else if (strcmp(ext, "dds") == 0) {
        image = new DDSImage(data);
      } else if (strcmp(ext, "png") == 0) {
        image = new PNGImage(data);
      } else {
        throw RuntimeError() << "Unknown image format of file '" << filename << "'";
      }
    } else {
      throw RuntimeError() << "Unknown image format of file '" << filename << "'";
    }

    return image;
  }
};

}

#endif // _H_STEEL_IMAGE
